package com.zhexinit.proxy.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.DefaultBHttpClientConnection;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.HttpProcessorBuilder;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;
import org.apache.http.util.EntityUtils;
import org.jsbd.boss.util.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.zhexinit.proxy.bean.HttpProxy;
import com.zhexinit.proxy.domain.HttpProxyLog;
import com.zhexinit.proxy.domain.ProxyHost;

import edu.hziee.common.queue.DelayExecuteBuffer;

public class ProxyServiceUtil {
    public static final Logger LOGGER = LoggerFactory.getLogger(ProxyServiceUtil.class);

    private static final String PROXY_HOST = PropertiesUtil.getValue("http.proxy.host", "http://120.26.6.201:14402");

    public static final String SDK_PROXY_QUERY_URL = PROXY_HOST
            + "/query?IMSI=${IMSI}&provinceId=${provinceId}&cityId=${cityId}";

    /**
     * 超时时间，单位为毫秒
     */
    private static final int TIMEOUT = 5000;

    /**
     * 超时时间，单位为毫秒
     */
    private static final int CONNECT_TIMEOUT = 5000;

    /**
     * socket连接任务池，没有找到HTTPCLIENT如何设置连接超时，故而自己做一个
     */
    final static ExecutorService exec = Executors.newFixedThreadPool(200);
    /**
     * 是否使用代理服务器的标志
     */
    public static final Boolean PROXY_FLAG = PropertiesUtil.getBoolean("proxy.flag", true);

    /**
     * 是否测试
     */

    public static final Boolean TEST_PROXY = PropertiesUtil.getBoolean("proxy.test", true);

    private static final int RETRY_COUNT = 3;
    
    


    public static String httpGet(HttpProxyLog httpProxyLog, DelayExecuteBuffer<HttpProxyLog> httpProxyLogBuffer) {
        long t = System.currentTimeMillis();
        for (int i = 0; i < RETRY_COUNT; i++) {
            HttpProcessor httpproc = HttpProcessorBuilder.create().add(new RequestContent())
                    .add(new RequestTargetHost()).add(new RequestConnControl())
                    .add(new RequestUserAgent(
                            "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1"))
                    .build();

            HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

            HttpCoreContext coreContext = HttpCoreContext.create();

            DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);

            ConnectionReuseStrategy connStrategy = DefaultConnectionReuseStrategy.INSTANCE;

            try {
                String url = httpProxyLog.getUrl();
                URL url1 = new URL(url);
                HttpHost host = new HttpHost(url1.getHost(), url1.getPort());
                coreContext.setTargetHost(host);
                if (!conn.isOpen()) {
                    connect(conn, url1.getHost(), url1.getPort(), httpProxyLog, 0);
                }
                BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("GET", url);

                for (Map.Entry<String, String> header : httpProxyLog.getHeaderMap().entrySet()) {
                    request.addHeader(header.getKey(), header.getValue());
                }
                if (httpProxyLog.getHttpContext().getCookies() != null
                        && !httpProxyLog.getHttpContext().getCookies().isEmpty()) {
                    request.addHeader("Cookie", getCookies(httpProxyLog.getHttpContext().getCookies()).toString());
                }
                LOGGER.debug(">> Request URI: " + request.getRequestLine().getUri());

                httpexecutor.preProcess(request, httpproc, coreContext);
                HttpResponse response = httpexecutor.execute(request, conn, coreContext);
                httpexecutor.postProcess(response, httpproc, coreContext);
                setCookieMap(response, httpProxyLog.getHttpContext().getCookies());
                LOGGER.info("<< Response: " + response.getStatusLine());
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                if (response.getStatusLine().getStatusCode() == 302) {
                    LOGGER.info("302跳转===============");
                    Header location = response.getLastHeader("Location");
                    HttpProxyLog httpProxyLog1 = (HttpProxyLog) BeanUtils.cloneBean(httpProxyLog);
                    httpProxyLogBuffer.add(httpProxyLog1);
                    httpProxyLog.setUrl(location.getValue());
                    return httpGet(httpProxyLog, httpProxyLogBuffer);
                }
                httpProxyLog.setResponseCode(response.getStatusLine().getStatusCode());
                httpProxyLog.setResponseContent(result);
                LOGGER.debug(result);
                LOGGER.debug("==============");
                if (!connStrategy.keepAlive(response, coreContext)) {
                    conn.close();
                } else {
                    LOGGER.debug("Connection kept alive...");
                }
                return result;
            } catch (IOException e) {
                LOGGER.error("Any error msg that u want to write", e);
                httpProxyLog.setResponseCode(-1);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (HttpException e) {
                LOGGER.error("Any error msg that u want to write", e);
                httpProxyLog.setResponseCode(-2);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (TimeoutException e) {
                LOGGER.error("连接超时", e);
                httpProxyLog.setResponseCode(-3);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (IllegalAccessException e) {
                LOGGER.error("Any error msg that u want to write", e);
            } catch (InstantiationException e) {
                LOGGER.error("Any error msg that u want to write", e);
            } catch (InvocationTargetException e) {
                LOGGER.error("Any error msg that u want to write", e);
            } catch (NoSuchMethodException e) {
                LOGGER.error("Any error msg that u want to write", e);
            } finally {
                try {
                    long t1 = System.currentTimeMillis();
                    httpProxyLog.setReadTime((int) (t1 - t));
                    boolean flag = httpProxyLogBuffer.add(httpProxyLog);
                    conn.close();
                } catch (Exception e) {
                    LOGGER.error("Any error msg that u want to write", e);
                }
            }
        }

        return null;
    }

    /**
     * getBytesWithSession:获取流结果
     * 
     * @author <a href="mailto:zhaihuilin@zhexinit.com" >翟惠林</a>
     * @param httpProxyLog
     * @param httpProxyLogBuffer
     * @return
     */
    public static byte[] httpDownload(HttpProxyLog httpProxyLog, DelayExecuteBuffer<HttpProxyLog> httpProxyLogBuffer) {
        long t = System.currentTimeMillis();
        for (int i = 0; i < RETRY_COUNT; i++) {
            HttpProcessor httpproc = HttpProcessorBuilder.create().add(new RequestContent())
                    .add(new RequestTargetHost()).add(new RequestConnControl())
                    .add(new RequestUserAgent(
                            "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1"))
                    .build();

            HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

            HttpCoreContext coreContext = HttpCoreContext.create();

            DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);

            ConnectionReuseStrategy connStrategy = DefaultConnectionReuseStrategy.INSTANCE;

            try {
                String url = httpProxyLog.getUrl();
                URL url1 = new URL(url);
                HttpHost host = new HttpHost(url1.getHost(), url1.getPort());
                coreContext.setTargetHost(host);
                if (!conn.isOpen()) {
                    connect(conn, url1.getHost(), url1.getPort(), httpProxyLog, 0);
                }
                BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("GET", url);

                for (Map.Entry<String, String> header : httpProxyLog.getHeaderMap().entrySet()) {
                    request.addHeader(header.getKey(), header.getValue());
                }
                if (httpProxyLog.getHttpContext().getCookies() != null
                        && !httpProxyLog.getHttpContext().getCookies().isEmpty()) {
                    request.addHeader("Cookie", getCookies(httpProxyLog.getHttpContext().getCookies()).toString());
                }
                LOGGER.debug(">> Request URI: " + request.getRequestLine().getUri());

                httpexecutor.preProcess(request, httpproc, coreContext);
                HttpResponse response = httpexecutor.execute(request, conn, coreContext);
                httpexecutor.postProcess(response, httpproc, coreContext);
                setCookieMap(response, httpProxyLog.getHttpContext().getCookies());
                LOGGER.info("<< Response: " + response.getStatusLine());
                byte[] result = EntityUtils.toByteArray(response.getEntity());

                httpProxyLog.setResponseCode(response.getStatusLine().getStatusCode());
                httpProxyLog.setResponseContent("this is bytes");
                httpProxyLog.setResponseContentBytes(result);
                LOGGER.debug("这是一个二进制结果");
                LOGGER.debug("==============");
                if (!connStrategy.keepAlive(response, coreContext)) {
                    conn.close();
                } else {
                    LOGGER.debug("Connection kept alive...");
                }
                return result;
            } catch (IOException e) {
                LOGGER.error("Any error msg that u want to write", e);
                httpProxyLog.setResponseCode(-1);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (HttpException e) {
                LOGGER.error("Any error msg that u want to write", e);
                httpProxyLog.setResponseCode(-2);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (TimeoutException e) {
                LOGGER.error("连接超时", e);
                httpProxyLog.setResponseCode(-3);
                httpProxyLog.setResponseContent(e.getMessage());
            } finally {
                try {
                    long t1 = System.currentTimeMillis();
                    httpProxyLog.setReadTime((int) (t1 - t));
                    boolean flag = httpProxyLogBuffer.add(httpProxyLog);
                    conn.close();
                } catch (Exception e) {
                    LOGGER.error("Any error msg that u want to write", e);
                }
            }
        }

        return null;
    }

    /**
     * 
     * 提交POST请求
     * 
     * @author <a href="mailto:zhaihuilin@zhexinit.com" >翟惠林</a>
     * @param url
     * @param httpProxyLog
     * @param httpProxyLogBuffer
     * @return
     */
    public static String httpPost(HttpProxyLog httpProxyLog, DelayExecuteBuffer<HttpProxyLog> httpProxyLogBuffer) {
        String url = httpProxyLog.getUrl();
        for (int i = 0; i < RETRY_COUNT; i++) {
            long t = System.currentTimeMillis();
            HttpProcessor httpproc = HttpProcessorBuilder.create().add(new RequestContent())
                    .add(new RequestTargetHost()).add(new RequestConnControl())
                    .add(new RequestUserAgent(
                            "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1"))
                    .build();
            HttpRequestExecutor httpexecutor = new HttpRequestExecutor();

            HttpCoreContext coreContext = HttpCoreContext.create();

            DefaultBHttpClientConnection conn = new DefaultBHttpClientConnection(8 * 1024);

            ConnectionReuseStrategy connStrategy = DefaultConnectionReuseStrategy.INSTANCE;
            try {
                URL url1 = new URL(url);
                HttpHost host = new HttpHost(url1.getHost(), url1.getPort());
                coreContext.setTargetHost(host);
                if (!conn.isOpen()) {
                    connect(conn, url1.getHost(), url1.getPort(), httpProxyLog, 0);
                }
                BasicHttpEntityEnclosingRequest request = new BasicHttpEntityEnclosingRequest("POST", url);

                for (Map.Entry<String, String> header : httpProxyLog.getHeaderMap().entrySet()) {
                    request.addHeader(header.getKey(), header.getValue());
                }
                if (httpProxyLog.getHttpContext().getCookies() != null
                        && !httpProxyLog.getHttpContext().getCookies().isEmpty()) {
                    request.addHeader("Cookie", getCookies(httpProxyLog.getHttpContext().getCookies()).toString());
                }
                Map<String, String> params = httpProxyLog.getParamMap();
                LOGGER.info("params [{}]", params);
                if (params != null && !params.isEmpty()) {
                    List<NameValuePair> httpParamsList = new ArrayList<NameValuePair>();
                    for (Map.Entry<String, String> param : params.entrySet()) {
                        NameValuePair e = new BasicNameValuePair(param.getKey(), param.getValue());
                        httpParamsList.add(e);
                    }
                    UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(httpParamsList, "UTF-8");
                    request.setEntity(postEntity);
                }
                LOGGER.debug(">> Request URI: " + request.getRequestLine().getUri());

                httpexecutor.preProcess(request, httpproc, coreContext);
                HttpResponse response = httpexecutor.execute(request, conn, coreContext);
                httpexecutor.postProcess(response, httpproc, coreContext);
                setCookieMap(response, httpProxyLog.getHttpContext().getCookies());
                LOGGER.info("<< Response: " + response.getStatusLine());
                String result = EntityUtils.toString(response.getEntity(), "UTF-8");
                httpProxyLog.setResponseCode(response.getStatusLine().getStatusCode());
                httpProxyLog.setResponseContent(result);
                LOGGER.info(result);
                LOGGER.debug("==============");
                if (!connStrategy.keepAlive(response, coreContext)) {
                    conn.close();
                } else {
                    LOGGER.debug("Connection kept alive...");
                }

                return result;
            } catch (IOException e) {
                LOGGER.error("Any error msg that u want to write", e);
                httpProxyLog.setResponseCode(-1);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (HttpException e) {
                LOGGER.error("Any error msg that u want to write", e);
                httpProxyLog.setResponseCode(-2);
                httpProxyLog.setResponseContent(e.getMessage());
            } catch (TimeoutException e) {
                LOGGER.error("连接超时", e);
                httpProxyLog.setResponseCode(-3);
                httpProxyLog.setResponseContent(e.getMessage());
            } finally {
                try {
                    long t1 = System.currentTimeMillis();
                    httpProxyLog.setReadTime((int) (t1 - t));
                    boolean flag = httpProxyLogBuffer.add(httpProxyLog);
                    conn.close();
                } catch (Exception e) {
                    LOGGER.error("Any error msg that u want to write", e);
                }
            }
        }
        return null;
    }

    /**
     * 
     * 描述：从浏览器响应中获取cookie数据存入map 编写者：袁淼 创建时间：2016年1月19日 下午4:09:38
     * </pre>
     * 
     * @param httpResponse
     * @param cookieMap
     */
    private static void setCookieMap(HttpResponse httpResponse, Map<String, String> cookieMap) {
        HeaderElementIterator it = new BasicHeaderElementIterator(httpResponse.headerIterator("Set-Cookie"));
        while (it.hasNext()) {
            HeaderElement elem = it.nextElement();
            cookieMap.put(elem.getName(), elem.getValue());
            LOGGER.debug(elem.getName() + " = " + elem.getValue());
        }
    }

    /**
     * 
     * 描述：将用map存储的cookie转换成字符串格式的cookie 编写者：袁淼 创建时间：2016年1月19日 下午3:48:30
     * </pre>
     * 
     * @param cookies
     * @return
     */
    private static StringBuilder getCookies(Map<String, String> cookies) {
        StringBuilder cookieBuilder = new StringBuilder();
        for (Map.Entry<String, String> entry : cookies.entrySet()) {
            cookieBuilder.append(entry.getKey() + "=" + entry.getValue() + ";");
        }
        LOGGER.debug("SETCOOKIES cookieMap[{}], cookieBuilder[{}]", new Object[] { cookies, cookieBuilder });
        return cookieBuilder;
    }

    private static void connect(final DefaultBHttpClientConnection conn, final String targetAddress,
            final int targetPort, final HttpProxyLog httpProxyLog, int retry) throws IOException, TimeoutException {
        long t = System.currentTimeMillis();

        int provinceId = Integer.valueOf(httpProxyLog.getHttpContext().getProvinceId());
        String imsi = httpProxyLog.getHttpContext().getImsi();
        int cityId = Integer.valueOf(httpProxyLog.getHttpContext().getCityId());
        final ProxyHost proxyHost = getProxyHost(imsi, provinceId, cityId);
        if (proxyHost != null) {
            httpProxyLog.setProxyHost(proxyHost.toString());
            httpProxyLog.setIpConnect(proxyHost.getIp());
            httpProxyLog.getHeaderMap().put("Ip", proxyHost.getIp());
            LOGGER.debug("使用代理服务器 imsi[{}],provinceId[{}],proxy[{}]", new Object[] { imsi, provinceId, proxyHost });
        } else {
            httpProxyLog.setProxyHost(null);
            LOGGER.error("代理服务器未获取到，请联系管理员检查代理服务器;imsi[{}] provinceId[{}] ", new Object[] { imsi, provinceId });
            return;
        }
        Callable<String> call = new Callable<String>() {
            public String call() throws Exception {
                Socket socket = new Socket(proxyHost.getAddress(), proxyHost.getPort());
                socket.setSoTimeout(TIMEOUT);
                socket.setReuseAddress(true);
                socket.setTcpNoDelay(true);
                conn.bind(socket);
                return "";
            }
        };
        try {
            Future<String> future = exec.submit(call);
            String obj = future.get(CONNECT_TIMEOUT, TimeUnit.MILLISECONDS); // 任务处理超时时间设为5秒
            LOGGER.debug("任务成功返回[{}]", proxyHost);
        } catch (TimeoutException ex) {
            LOGGER.warn("proxy host[{}] 链接超时", httpProxyLog.getProxyHost());

            if (conn != null) {
                conn.close();
            }
            long t1 = System.currentTimeMillis();
            httpProxyLog.setConnTime((int) (t1 - t));
            throw ex;
        } catch (Exception e) {
            LOGGER.warn("proxy host[{}] 链接超时", new Object[] { httpProxyLog.getProxyHost(), e });
        }

        long t1 = System.currentTimeMillis();
        httpProxyLog.setConnTime((int) (t1 - t));
    }

    /**
     * 
     * getProxyService:查询代理服务器及长连接IP
     * 
     * @author <a href="mailto:wulei@zhexinit.com" >吴磊</a>
     * @param imsi
     * @param provinceId
     * @param cityId
     * @return
     */
    public static ProxyHost getProxyHost(String imsi, int provinceId, int cityId) {
        ProxyHost proxyHost = new ProxyHost();
        String url = SDK_PROXY_QUERY_URL;
        url = url.replaceAll("\\$\\{IMSI\\}", imsi);
        url = url.replaceAll("\\$\\{provinceId\\}", provinceId + "");
        url = url.replaceAll("\\$\\{cityId\\}", cityId + "");
        CloseableHttpClient client = HttpClientBuilder.create().build();
        HttpGet get = new HttpGet(url);
        String result = null;
        try {
            HttpResponse response = client.execute(get);
            ByteArrayOutputStream outstream = new ByteArrayOutputStream();
            response.getEntity().writeTo(outstream);
            result = outstream.toString();
            LOGGER.debug("getProxyService url is[{}], response info is[{}]", new Object[] { url, result });
            if (StringUtils.isNotBlank(result)) {
                Gson gson = new Gson();
                HttpProxy httpProxy = gson.fromJson(result, HttpProxy.class);
                if (httpProxy.getResult() == 0) {
                    proxyHost.setIp(httpProxy.getProxyinfo().getIpConnect());
                    proxyHost.setAddress(httpProxy.getProxyservice().getHost());
                    proxyHost.setPort(httpProxy.getProxyservice().getPort());
                }
            }
        } catch (Exception e) {
            LOGGER.error("查询代理服务器error[{}],请求Url[{}],响应结果[{}]", new Object[] { e, url, result });
        } finally {
            get.releaseConnection();
        }
        return proxyHost;
    }

}
